import re
import time
import pickle
import random
import ddddocr
import requests
from typing import List
from loguru import logger
from datetime import datetime
from bs4 import BeautifulSoup

class EETOP:
    def __init__(self, username='', password='', max_try_ocr=6):
        headers = {
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0',
            'Accept-Encoding': 'gzip, deflate, br, zstd',
            'Accept-Language': 'zh-CN,zh;q=0.9',
            'Priority': 'u=0, i',
            'Referer': 'https://bbs.eetop.cn/member.php?mod=logging&action=login',
            'Origin': 'https://bbs.eetop.cn',
            'Sec-Ch-Ua': '"Chromium";v="136", "Microsoft Edge";v="136", "Not.A/Brand";v="99"',
            'Sec-Ch-Ua-Mobile': '?0',
            'Sec-Ch-Ua-Platform': '"Windows"',
            'Sec-Fetch-Dest': 'document',
            'Sec-Fetch-Mode': 'navigate',
            'Sec-Fetch-Site': 'same-origin',
            'Sec-Fetch-User': '?1',
            'Upgrade-Insecure-Requests': '1',
            'Connection': 'keep-alive',
            'Content-Type': 'application/x-www-form-urlencoded'
        }
        self.home_page_url = headers['Origin']
        self.login_page_url = headers['Referer']
        self.last_page_url = None
        self.username = username
        self.password = password
        self.session = requests.Session()
        self.session.headers.update(headers)
        self.ocr = ddddocr.DdddOcr(det=False, ocr=True, show_ad=False)
        self.max_try_ocr = max_try_ocr
        logger.add("eetop.log")

    def file_prefix(self) -> str:
        return datetime.now().strftime("%Y%m%d%H%M%S")

    # 图片识别
    def image_classification(self, image) -> str | None:
        captcha_code = None
        try:
            captcha_code = self.ocr.classification(image)
        except Exception as e:
            logger.error(f"图片识别失败: {e}")
        return captcha_code

    # get 请求
    def request_get(self, url, **kwargs):
        try:
            response = self.session.get(url, **kwargs)
            response.raise_for_status()
            return response
        except requests.RequestException as e:
            logger.error(f"GET 请求失败: {e}")
            return None

    # post 请求
    def request_post(self, url, data=None, **kwargs):
        try:
            response = self.session.post(url, data=data, **kwargs)
            response.raise_for_status()
            return response
        except requests.RequestException as e:
            logger.error(f"POST 请求失败: {e}")
            return None

    # 保存会话
    def save_session(self, file_path='session.pkl') -> bool:
        try:
            with open(file_path, 'wb') as f:
                pickle.dump(self.session, f)
            return True
        except Exception as e:
            logger.error(f"保存会话失败: {e}")
            return False

    # 加载会话
    def load_session(self, file_path='session.pkl') -> bool:
        try:
            with open(file_path, 'rb') as f:
                self.session = pickle.load(f)
            return True
        except Exception as e:
            logger.error(f"加载会话失败: {e}")
            return False

    # 提取 formhash
    def extract_formhash(self, html_content):
        formhash_match = re.search(r'<input type="hidden" name="formhash" value="([^"]+)" />', html_content)
        if not formhash_match:
            return None
        return formhash_match.group(1)

    # 提取 loginhash
    def extract_loginhash(self, html_content):
        loginhash_match = re.search(r'id="username_([a-zA-Z0-9]+)"', html_content)
        if not loginhash_match:
            return None
        return loginhash_match.group(1)

    # 提取 idhash
    def extract_idhash(self, html_content):
        idhash_match = re.search(r"updateseccode\('([^']+)'", html_content)
        if not idhash_match:
            idhash_match = re.search(r'<span id="seccode_([^"]+)"></span>', html_content)
            if not idhash_match:
                return None
        return idhash_match.group(1)

    # 提取 fid
    def extract_fid(self, html_content):
        fid_match = re.search(r'fid=(\d+)', html_content)
        if not fid_match:
            return None
        return fid_match.group(1)

    # 解析登录页面
    def parse_login_page(self, html_content):
        formhash = self.extract_formhash(html_content)
        loginhash = self.extract_loginhash(html_content)
        idhash = self.extract_idhash(html_content)
        flag = True
        if not formhash:
            logger.error("无法提取 formhash")
            flag = False
        if not loginhash:
            logger.error("无法提取 loginhash")
            flag = False
        if not idhash:
            logger.error("无法提取 idhash")
            flag = False
        return flag, (formhash, loginhash, idhash)

    # 获取登录验证码
    def get_login_captcha_code(self, captcha_idhash) -> str | None:
        update_script_url = f"https://bbs.eetop.cn/misc.php?mod=seccode&action=update&idhash={captcha_idhash}&{random.random()}&modid=member::logging"
        script_response = self.request_get(update_script_url, timeout=15)
        if not script_response:
            logger.error("获取验证码脚本失败")
            return None
        script_content = script_response.text
        with open(f'data/{self.file_prefix()}_captcha_script.html', 'w', encoding='utf-8') as f:
            f.write(script_content)

        captcha_img_params_match = re.search(r'src="misc\.php\?mod=seccode&update=(\d+)&idhash=([^"]+)"', script_content)
        if not captcha_img_params_match:
            logger.error("无法提取验证码图片参数")
            return None

        update_value = captcha_img_params_match.group(1)
        img_idhash = captcha_img_params_match.group(2)
        captcha_image_url = f"https://bbs.eetop.cn/misc.php?mod=seccode&update={update_value}&idhash={img_idhash}"
        captcha_response = self.request_get(captcha_image_url, timeout=15)
        if not captcha_response:
            logger.error("获取验证码图片失败")
            return None
        with open(f'data/{self.file_prefix()}_login_captcha.png', 'wb') as f:
            f.write(captcha_response.content)

        return self.image_classification(captcha_response.content)

    # 验证 login 状态
    def verify_login(self) -> bool:
        resp = self.request_get('https://bbs.eetop.cn/')
        if resp is None:
            return False
        home_html_content = resp.text
        with open(f'data/{self.file_prefix()}_home_page.html', 'w', encoding='utf-8') as f:
            f.write(home_html_content)

        if f'>{self.username}</a>' in home_html_content or 'action=logout' in home_html_content:
            user_link_pattern = re.compile(r'<a[^>]+href="[^"]*space-uid-\d+\.html"[^>]*>'+re.escape(self.username)+r'</a>', re.IGNORECASE)
            logout_link_pattern = re.compile(r'action=logout(&|&)formhash=([0-9a-f]{8})')
            if user_link_pattern.search(home_html_content) or logout_link_pattern.search(home_html_content):
                return True
        return False

    # 验证 captcha
    def verify_captcha(self, captcha_idhash, captcha_code, modid_type) -> bool:
        modid = None
        if modid_type == 'LOGIN':
            modid = 'member::logging'
        elif modid_type == 'FORUM':
            modid = 'forum::ajax'
        verify_captcha_url = f'https://bbs.eetop.cn/misc.php?mod=seccode&action=check&inajax=1&modid={modid}&idhash={captcha_idhash}&secverify={captcha_code}'
        verify_resp = self.request_get(verify_captcha_url, timeout=20)
        if not verify_resp:
            logger.error("验证码验证请求失败")
            return False
        return 'succeed' in verify_resp.text

    # 登录
    def login(self) -> bool:
        login_page_resp = self.request_get(self.login_page_url, timeout=20)
        if not login_page_resp:
            logger.error("获取登录页面失败")
            return False
        with open(f'data/{self.file_prefix()}_login_page.html', 'w', encoding='utf-8') as f:
            f.write(login_page_resp.text)

        flag, (formhash, loginhash, idhash) = self.parse_login_page(login_page_resp.text)
        if flag is False:
            logger.error("解析登录页面失败")
            return False
        login_data = {
            'formhash':formhash,
            'referer': self.login_page_url,
            'username': self.username,
            'password': self.password,
            'questionid': '0',
            'answer': '',
            'cookietime': '31536000', # 自动登录时间（1年）
            'seccodehash': idhash,
            'seccodemodid': 'member::logging',
            'seccodeverify': '0000', # 用户输入的 captcha
        }
        captcha_ready = False
        for _ in range(self.max_try_ocr):
            captcha_code = self.get_login_captcha_code(idhash)
            if captcha_code is None:
                logger.warning("获取 captcha 失败")
                continue
            logger.info(f'captcha: {captcha_code}')
            if self.verify_captcha(idhash, captcha_code, 'LOGIN'):
                captcha_ready = True
                logger.info('captcha 验证成功')
                break
            else:
                logger.warning('captcha 验证失败, 正在重试')
                time.sleep(5)
                continue
        if captcha_ready is False:
            logger.error('captcha 验证失败')
            return False
        login_url = f"https://bbs.eetop.cn/member.php?mod=logging&action=login&loginsubmit=yes&loginhash={loginhash}"
        login_data.update({'seccodeverify': captcha_code})
        login_resp = self.request_post(login_url, data=login_data, timeout=20)
        if not login_resp:
            logger.error("登录请求失败")
            return False
        if self.verify_login():
            logger.info('登录成功')
            return True
        logger.error('登录失败')
        return False

    # 验证 logout 状态
    def verify_logout(self) -> bool:
        resp = self.request_get(self.home_page_url)
        if not resp:
            logger.error("获取首页失败")
            return False
        if '手机号码，快捷登录' in resp.text and '登录' in resp.text:
            return True
        return False

    # 登出
    def logout(self) -> bool:
        html_resp = self.request_get(self.home_page_url, timeout=20)
        if not html_resp:
            logger.error("获取首页失败")
            return False
        html_content = html_resp.text
        formhash = self.extract_formhash(html_content)
        logout_url = f'https://bbs.eetop.cn/member.php?mod=logging&action=logout&formhash={formhash}'
        self.session.headers['Referer'] = self.home_page_url
        if not self.request_get(logout_url, timeout=25, allow_redirects=True):
            logger.error("登出请求失败")
            return False
        return self.verify_logout()

    # 解析 forum 页面
    def parse_forum_page(self, fid, page_num) -> List[dict]:
        threads_data = []
        forum_page_url = f'https://bbs.eetop.cn/forum.php?mod=forumdisplay&fid={fid}&filter=author&orderby=dateline&page={page_num}'

        # 动态构造 Referer
        referer_url = f'https://bbs.eetop.cn/forum-{fid}-1.html'
        if page_num != 1:
            referer_url = referer_url = f'https://bbs.eetop.cn/forum.php?mod=forumdisplay&fid={fid}&filter=author&orderby=dateline&page={page_num-1}'

        self.session.headers['Referer'] = referer_url

        resp = self.request_get(forum_page_url, timeout=20)
        if not resp:
            logger.error("获取论坛页面失败")
            return []

        html_content = resp.text
        with open(f'data/{self.file_prefix()}_forum_page.html', 'w', encoding='utf-8') as f:
            f.write(html_content)

        soup = BeautifulSoup(html_content, 'lxml')
        threadlist_table = soup.find('table', id='threadlisttableid')

        if not threadlist_table:
            return threads_data

        # 只查找普通帖子 (其 tbody id 以 "normalthread_" 开头)
        normal_thread_tbodys = threadlist_table.find_all('tbody', id=lambda x: x and x.startswith('normalthread_'))

        for tbody in normal_thread_tbodys:
            # 在每个普通帖子的tbody中，查找包含标题的<th>元素
            # 标题链接<a>通常在class为'common'或'new'的<th>内部，并且class为's xst'
            title_th = tbody.find('th', class_=['common', 'new', 'lock', 'hot'])
            if not title_th:
                continue
            title_link_tag = title_th.find('a', class_='s xst', href=re.compile(r"thread-\d+-\d+-\d+\.html"))

            if not title_link_tag:
                continue

            # 提取帖子标题
            thread_title = title_link_tag.get_text(strip=True)

            # 提取帖子URL
            thread_url = title_link_tag['href']

            # 从URL中提取帖子ID (tid)
            tid_match = re.search(r"thread-(\d+)-", thread_url)
            thread_id = tid_match.group(1) if tid_match else None
            if not thread_id:
                continue

            # 提取作者名
            # 作者信息通常在class为'by'的<td>内的<cite>标签中
            author_cell = tbody.find('td', class_='by')
            thread_author = "N/A" # 默认值
            if author_cell:
                author_cite = author_cell.find('cite')
                if author_cite:
                    author_link = author_cite.find('a') # 作者通常有链接到其个人主页
                    if author_link:
                        thread_author = author_link.get_text(strip=True)
                    else: # 如果没有<a>标签 (例如匿名用户或特殊情况)，直接取<cite>文本
                        thread_author = author_cite.get_text(strip=True)

            threads_data.append({
                'id': thread_id,
                'title': thread_title,
                'url': thread_url,
                'author': thread_author,
            })

        return threads_data

    # 解析 forum 总页数
    def parse_forum_total_pages(self, fid) -> int:
        forum_page_url = f'https://bbs.eetop.cn/forum.php?mod=forumdisplay&fid={fid}&filter=author&orderby=dateline&page=1'
        resp = self.request_get(forum_page_url, timeout=20)
        if resp is None:
            logger.error("获取 forum 页面失败")
            return 0

        html_content = resp.text
        with open(f'data/{self.file_prefix()}_forum_page.html', 'w', encoding='utf-8') as f:
            f.write(html_content)

        soup = BeautifulSoup(html_content, 'lxml')

        pg_div = None

        # 尝试多种可能的位置找到翻页控件
        pg_containers = soup.find_all('div', class_='pg') # 主要的翻页控件
        if not pg_containers:
            pg_containers = soup.find_all('span', id=['fd_page_top', 'fd_page_bottom'])

        for container in pg_containers:
            if container.name == 'span' and container.find('div', class_='pg'): # 如果span内嵌了div.pg
                pg_div = container.find('div', class_='pg')
                break
            elif container.name == 'div' and container.has_attr('class') and 'pg' in container['class']:
                pg_div = container
                break

        if pg_div:
            # 查找包含总页数信息的 <label> 标签下的 <span>
            label_tag = pg_div.find('label')
            if label_tag:
                page_info_span = label_tag.find('span', title=re.compile(r"共 \d+ 页"))
                if page_info_span is None:
                    return 1
                total_pages_match = re.search(r"共 (\d+) 页", page_info_span['title'])
                if total_pages_match is None:
                    return 1
                total_pages = int(total_pages_match.group(1))
                return total_pages

        return 1

    # 获取回复验证码
    def get_replay_captcha_code(self, captcha_idhash) -> str | None:
        update_script_url = f"https://bbs.eetop.cn/misc.php?mod=seccode&action=update&idhash={captcha_idhash}&{random.random()}&modid=forum::ajax"
        script_response = self.request_get(update_script_url, timeout=15)
        if not script_response:
            logger.error("获取验证码更新脚本失败")
            return None
        script_content = script_response.text
        with open(f'data/{self.file_prefix()}_replay_captcha_script.json', 'w', encoding='utf-8') as f:
            f.write(script_content)

        captcha_img_params_match = re.search(r'src="misc\.php\?mod=seccode&update=(\d+)&idhash=([^"]+)"', script_content)
        if not captcha_img_params_match:
            logger.error("无法提取验证码图片参数")
            return None
        update_value = captcha_img_params_match.group(1)
        img_idhash = captcha_img_params_match.group(2)
        captcha_image_url = f"https://bbs.eetop.cn/misc.php?mod=seccode&update={update_value}&idhash={img_idhash}"
        captcha_response = self.request_get(captcha_image_url, timeout=15)
        if not captcha_response:
            logger.error("获取验证码图片失败")
            return None
        with open(f'data/{self.file_prefix()}_reply_captcha.png', 'wb') as f:
            f.write(captcha_response.content)

        return self.image_classification(captcha_response.content)

    # 快速回复
    def quick_reply(self, thread_url, message) -> bool:
        # 提取 thread_id
        thread_id_match = re.search(r'thread-(\d+)', thread_url)
        if not thread_id_match:
            logger.error("无效的 thread URL")
            return False
        thread_id = thread_id_match.group(1)

        # 访问 thread_url 获取 formhash
        thread_resp = self.request_get(thread_url, timeout=20)
        if not thread_resp:
            logger.error("获取线程页面失败")
            return False
        with open(f'data/{self.file_prefix()}_thread_page.html', 'w', encoding='utf-8') as f:
            f.write(thread_resp.text)

        formhash = self.extract_formhash(thread_resp.text)
        if not formhash:
            logger.error("无法提取 formhash")
            return False

        fid = self.extract_fid(thread_resp.text)
        if fid is None:
            logger.error("无法提取 fid")
            return False

        # 准备回复数据
        reply_data = {
            'formhash': formhash,
            'message': message,
            'posttime': str(int(time.time())),
            'wysiwyg': '1',
            'checkbox': '0',
            'handlekey': 'vfastpost',
            'usesig': '1',
            'subject': ''
        }

        # 检查是否需要 captcha
        check_url = "https://bbs.eetop.cn/forum.php?mod=ajax&action=checkpostrule&ac=reply&inajax=1&ajaxtarget=vfastpostseccheck"
        check_resp = self.request_get(check_url, timeout=20)
        if not check_resp:
            logger.error("获取检查帖子 captcha 页面失败")
            return False
        with open(f'data/{self.file_prefix()}_check_captcha.xml', 'w', encoding='utf-8') as f:
            f.write(check_resp.text)

        if 'seccode' in check_resp.text:
            captcha_idhash = self.extract_idhash(check_resp.text)
            if not captcha_idhash:
                logger.error("无法提取 captcha idhash")
                return False

            # 添加 captcha 数据到回复数据
            reply_data.update({
                'seccodehash': captcha_idhash,
                'seccodemodid': 'forum::ajax'
            })

            # 多次尝试 OCR
            captcha_ready = False
            for _ in range(self.max_try_ocr):
                captcha_code = self.get_replay_captcha_code(captcha_idhash)
                if captcha_code is None:
                    logger.warning("获取 captcha 失败")
                    continue
                logger.info(f'captcha: {captcha_code}')
                if self.verify_captcha(captcha_idhash, captcha_code, 'FORUM'):
                    reply_data.update({'seccodeverify': captcha_code})
                    logger.info('captcha 验证成功')
                    captcha_ready = True
                    break
                else:
                    logger.warning('captcha 验证失败, 正在重试')
                    time.sleep(5)
                    continue
        if captcha_ready is False:
            logger.error('captcha 验证失败')
            return False

        # 发送回复请求
        reply_url = f"https://bbs.eetop.cn/forum.php?mod=post&action=reply&fid={fid}&tid={thread_id}&fromvf=1&extra=page=3&replysubmit=yes&infloat=yes&handlekey=vfastpost&inajax=1"
        reply_resp = self.request_post(reply_url, data=reply_data, timeout=20)
        if not reply_resp:
            logger.error("发送回复请求失败")
            return False
        with open(f'data/{self.file_prefix()}_reply_response.xml', 'w', encoding='utf-8') as f:
            f.write(reply_resp.text)

        # 解析 XML 响应
        if 'succeedhandle_vfastpost' in reply_resp.text:
            if '回复需要审核，请等待通过' in reply_resp.text:
                logger.warning('回复需要审核，请等待通过')
                return False
            elif '非常感谢，回复发布成功，现在将转入主题页' in reply_resp.text:
                logger.info('回复成功')
                return True
            return False
        logger.error('回复失败')
        return False

    # 自动快速回复
    def auto_quick_reply(self, forum_id: int, page_num: int, reply_messages: List[str]):
        thread_list = self.parse_forum_page(forum_id, page_num)
        reply_count = 0
        for thread in thread_list:
            if thread['author'] == self.username:
                continue
            logger.info(f"thread title: {thread['title']}")
            message = random.choice(reply_messages)
            logger.info(f"reply message: {message}")
            if self.quick_reply(thread['url'], message):
                reply_count += 1
            seconds = random.randint(60, 100)
            logger.info(f"等待 {seconds} 秒后继续")
            print('\n\n\n')
            time.sleep(seconds)
        logger.info(f"本次自动回复成功 {reply_count} 条")
